package com.ibm.iisp.common.util;

import java.beans.PropertyDescriptor;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;

public class UriUtils {
	private static Logger logger = LoggerFactory.getLogger(UriUtils.class);

	static boolean isSimpleType(Object obj) {
		return BeanUtils.isSimpleProperty(obj.getClass());
	}

	/**
	 * @param obj The Object will be converted
	 * @return parameters map
	 * @throws Exception any exception
	 */
	public static Map<String, Object> toQueryParams(Object obj) throws Exception {
		Map<String, Object> params = new HashMap<String, Object>();
		HashSet<Object> visited = new HashSet<>();
		toQueryParams(obj, "", params, visited);
		return params;
	}

	private static void toQueryParams(Object obj, String baseParamName, Map<String, Object> params, Set<Object> visited)
			throws Exception {
		if (obj == null || !visited.add(obj)) {
			return;
		}
		String preParam = baseParamName;
		if (preParam != null && preParam.length() > 0) {
			preParam += ".";
		}
		if (isSimpleType(obj)) {
			params.put(baseParamName, formatValue(obj));
		} else {
			if (obj.getClass().isArray() || obj instanceof Iterable) {
				int i = 0;
				Iterable<?> it = null;
				if (obj.getClass().isArray()) {
					it = Arrays.asList((Object[]) obj);
				} else {
					it = (Iterable<?>) obj;
				}
				for (Object subObj : it) {
					toQueryParams(subObj, baseParamName + "[" + i + "]", params, visited);
					i++;
				}
			} else {
				PropertyDescriptor[] props = BeanUtils.getPropertyDescriptors(obj.getClass());
				for (PropertyDescriptor prop : props) {
					if (prop.getReadMethod() == null || prop.getWriteMethod() == null) {
						continue;
					}
					String name = prop.getName();
					Object val = prop.getReadMethod().invoke(obj);
					if (val != null) {
						toQueryParams(val, preParam + name, params, visited);
					}
				}
			}
		}
	}

	/**
	 * @param obj           The Object will be converted
	 * @param baseParamName parameter name base. e.g. user, then all parameter name
	 *                      will be like user.xxx
	 * @return query string start with &amp;, e.g.
	 *         &amp;user.name=Joe&amp;user.code=12345
	 */
	public static String toQueryString(Object obj, String baseParamName) {
		String url = "";
		String preParam = baseParamName;
		if (preParam != null && preParam.length() > 0) {
			preParam += ".";
		}
		if (obj == null) {
			return url;
		}
		if (isSimpleType(obj)) {
			url = url + "&" + baseParamName + "=" + formatValue(obj);
		} else {
			if (obj.getClass().isArray() || obj instanceof Iterable) {
				int i = 0;
				Iterable<?> it = null;
				if (obj.getClass().isArray()) {
					it = Arrays.asList((Object[]) obj);
				} else {
					it = (Iterable<?>) obj;
				}
				for (Object subObj : it) {
					if (isSimpleType(subObj)) {
						url = url + "&" + baseParamName + "[" + i + "]" + "=" + formatValue(subObj);
					} else {
						url = url + toQueryString(subObj, baseParamName + "[" + i + "]");
					}
					i++;
				}
			} else {
//				Field[] fields = obj.getClass().getDeclaredFields();
				// PropertyDescriptor[] props = PropertyUtils.getPropertyDescriptors(obj);
				PropertyDescriptor[] props = BeanUtils.getPropertyDescriptors(obj.getClass());
				for (PropertyDescriptor prop : props) {
					if (prop.getReadMethod() == null || prop.getWriteMethod() == null) {
						continue;
					}
					String name = prop.getName();
					logger.trace("get {}.{}", baseParamName, name);
					Object val = null;
					try {
						val = prop.getReadMethod().invoke(obj);
					} catch (Exception e) {
						logger.warn("Error to get value of {}.{}", baseParamName, name, e);
					}
					if (val != null) {
						url = url + toQueryString(val, preParam + name);
					}
				}
			}
		}
		return url;
	}

	static String formatValue(Object val) {
		if (val instanceof Date) {
			return String.valueOf(((Date) val).getTime());
		}
		return String.valueOf(val);
	}

}
